/**
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; version 2 of the License.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY. 
 * See the GNU General Public License for more details.
 * 
 * Copyright (C) 2011 CellObject.net
 */
package net.cellobject.javacodeformatter.view;

import java.util.Comparator;

import net.cellobject.javacodeformatter.action.FormatterViewFilterAction;
import net.cellobject.javacodeformatter.contribution.RemoveFormatterContributionItem;
import net.cellobject.javacodeformatter.handler.RemoveFormatterHandler;
import net.cellobject.javacodeformatter.handler.RenameFormatterHandler;
import net.cellobject.javacodeformatter.model.FormatterManager;
import net.cellobject.javacodeformatter.model.IFormatterItem;
import net.cellobject.javacodeformatter.util.EditorUtil;

import org.eclipse.core.commands.IHandler;
import org.eclipse.jface.action.GroupMarker;
import org.eclipse.jface.action.IMenuListener;
import org.eclipse.jface.action.IMenuManager;
import org.eclipse.jface.action.IToolBarManager;
import org.eclipse.jface.action.MenuManager;
import org.eclipse.jface.action.Separator;
import org.eclipse.jface.layout.TableColumnLayout;
import org.eclipse.jface.viewers.CellEditor;
import org.eclipse.jface.viewers.ColumnLabelProvider;
import org.eclipse.jface.viewers.ColumnPixelData;
import org.eclipse.jface.viewers.ColumnWeightData;
import org.eclipse.jface.viewers.EditingSupport;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.ISelectionChangedListener;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.SelectionChangedEvent;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.jface.viewers.TableViewer;
import org.eclipse.jface.viewers.TableViewerColumn;
import org.eclipse.jface.viewers.TextCellEditor;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.KeyAdapter;
import org.eclipse.swt.events.KeyEvent;
import org.eclipse.swt.events.MouseAdapter;
import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Menu;
import org.eclipse.swt.widgets.Table;
import org.eclipse.swt.widgets.TableColumn;
import org.eclipse.ui.IMemento;
import org.eclipse.ui.ISelectionListener;
import org.eclipse.ui.IViewSite;
import org.eclipse.ui.IWorkbenchActionConstants;
import org.eclipse.ui.IWorkbenchPart;
import org.eclipse.ui.PartInitException;
import org.eclipse.ui.handlers.IHandlerActivation;
import org.eclipse.ui.handlers.IHandlerService;
import org.eclipse.ui.part.ViewPart;
import org.eclipse.ui.texteditor.IWorkbenchActionDefinitionIds;

public class FormatterView extends ViewPart {
	public static final String ID = "net.cellobject.javacodeformatter.view.FormatterView";

	private TableViewer viewer;

	private TableColumn typeColumn;

	private TableColumn nameColumn;

	private TableColumn locationColumn;

	private FormatterViewSorter sorter;

	private FormatterViewFilterAction filterAction;

	private IHandler removeHandler;

	private RemoveFormatterContributionItem removeContributionItem;

	private ISelectionListener pageSelectionListener;

	private IMemento memento;

	/**
	 * The constructor.
	 */
	public FormatterView() {
	}

	/**
	 * This is a callback that will allow us to create the viewer and initialize it.
	 */
	@Override
	public void createPartControl(final Composite parent) {
		createTableViewer(parent);
		createTableSorter();
		createContributions();
		createContextMenu();
		createToolbarButtons();
		createViewPulldownMenu();
		hookKeyboard();
		hookGlobalHandlers();
		hookDragAndDrop();
		createInlineEditor();
		hookMouse();
		hookPageSelection();
	}

	private void createTableViewer(final Composite parent) {
		viewer = new TableViewer(parent, SWT.H_SCROLL | SWT.V_SCROLL | SWT.MULTI | SWT.FULL_SELECTION);
		final Table table = viewer.getTable();
		final TableColumnLayout layout = new TableColumnLayout();
		parent.setLayout(layout);
		typeColumn = new TableColumn(table, SWT.LEFT);
		typeColumn.setText("");
		layout.setColumnData(typeColumn, new ColumnPixelData(18));
		nameColumn = new TableColumn(table, SWT.LEFT);
		nameColumn.setText("File Name");
		layout.setColumnData(nameColumn, new ColumnWeightData(4));
		locationColumn = new TableColumn(table, SWT.LEFT);
		locationColumn.setText("Location");
		layout.setColumnData(locationColumn, new ColumnWeightData(9));
		table.setHeaderVisible(true);
		table.setLinesVisible(false);
		viewer.setContentProvider(new FormatterViewContentProvider());
		viewer.setLabelProvider(new FormatterViewLabelProvider());
		viewer.setInput(FormatterManager.getManager());
		getSite().setSelectionProvider(viewer);
	}

	@SuppressWarnings("unchecked")
	private void createTableSorter() {
		final Comparator<IFormatterItem> nameComparator = new Comparator<IFormatterItem>() {
			public int compare(final IFormatterItem i1, final IFormatterItem i2) {
				return i1.getName().compareTo(i2.getName());
			}
		};
		final Comparator<IFormatterItem> locationComparator = new Comparator<IFormatterItem>() {
			public int compare(final IFormatterItem i1, final IFormatterItem i2) {
				return i1.getLocation().compareTo(i2.getLocation());
			}
		};
		final Comparator<IFormatterItem> typeComparator = new Comparator<IFormatterItem>() {
			public int compare(final IFormatterItem i1, final IFormatterItem i2) {
				return i1.getType().compareTo(i2.getType());
			}
		};
		sorter = new FormatterViewSorter(viewer, new TableColumn[]{nameColumn, locationColumn, typeColumn},
				new Comparator[]{nameComparator, locationComparator, typeComparator});
		if (memento != null) {
			sorter.init(memento);
		}
		viewer.setSorter(sorter);
	}

	private void createContributions() {
		removeHandler = new RemoveFormatterHandler();
		removeContributionItem = new RemoveFormatterContributionItem(getViewSite(), removeHandler);
	}

	private void createContextMenu() {
		final MenuManager menuMgr = new MenuManager("#PopupMenu");
		menuMgr.setRemoveAllWhenShown(true);
		menuMgr.addMenuListener(new IMenuListener() {
			public void menuAboutToShow(final IMenuManager m) {
				FormatterView.this.fillContextMenu(m);
			}
		});
		final Menu menu = menuMgr.createContextMenu(viewer.getControl());
		viewer.getControl().setMenu(menu);
		getSite().registerContextMenu(menuMgr, viewer);
	}

	private void fillContextMenu(final IMenuManager menuMgr) {
		menuMgr.add(new Separator("edit"));
		menuMgr.add(removeContributionItem);
		menuMgr.add(new Separator(IWorkbenchActionConstants.MB_ADDITIONS));
	}

	private void createToolbarButtons() {
		final IToolBarManager toolBarMgr = getViewSite().getActionBars().getToolBarManager();
		toolBarMgr.add(new GroupMarker("edit"));
		toolBarMgr.add(removeContributionItem);
	}

	private void createViewPulldownMenu() {
		final IMenuManager menu = getViewSite().getActionBars().getMenuManager();
		filterAction = new FormatterViewFilterAction(viewer, "Filter...");
		if (memento != null) {
			filterAction.init(memento);
		}
		menu.add(filterAction);
	}

	private void hookKeyboard() {
		viewer.getControl().addKeyListener(new KeyAdapter() {
			@Override
			public void keyReleased(final KeyEvent event) {
				handleKeyReleased(event);
			}
		});
	}

	protected void handleKeyReleased(final KeyEvent event) {
		if ( (event.keyCode == SWT.F2) && (event.stateMask == 0)) {
			new RenameFormatterHandler().editElement(this);
		}
		if ( (event.character == SWT.DEL) && (event.stateMask == 0)) {
			removeContributionItem.run();
		}
	}

	private void hookGlobalHandlers() {
		// getViewSite().getActionBars().setGlobalActionHandler(
		// ActionFactory.CUT.getId(), cutAction);
		// getViewSite().getActionBars().setGlobalActionHandler(
		// ActionFactory.COPY.getId(), copyAction);
		// getViewSite().getActionBars().setGlobalActionHandler(
		// ActionFactory.PASTE.getId(), pasteAction);
		final IHandlerService handlerService = (IHandlerService)getViewSite().getService(IHandlerService.class);
		viewer.addSelectionChangedListener(new ISelectionChangedListener() {
			private IHandlerActivation removeActivation;

			@SuppressWarnings("deprecation")
			public void selectionChanged(final SelectionChangedEvent event) {
				if (event.getSelection().isEmpty()) {
					if (removeActivation != null) {
						handlerService.deactivateHandler(removeActivation);
						removeActivation = null;
					}
				} else {
					if (removeActivation == null) {
						removeActivation = handlerService.activateHandler(IWorkbenchActionDefinitionIds.DELETE, removeHandler);
					}
				}
			}
		});
	}

	private void hookDragAndDrop() {
		new FormatterDragSource(viewer);
		new FormatterDropTarget(viewer);
	}

	private void hookMouse() {
		viewer.getTable().addMouseListener(new MouseAdapter() {
			@Override
			public void mouseDoubleClick(final MouseEvent e) {
				EditorUtil.openEditor(getSite().getPage(), viewer.getSelection());
			}
		});
	}

	private void hookPageSelection() {
		pageSelectionListener = new ISelectionListener() {
			public void selectionChanged(final IWorkbenchPart part, final ISelection selection) {
				pageSelectionChanged(part, selection);
			}
		};
		getSite().getPage().addPostSelectionListener(pageSelectionListener);
	}

	protected void pageSelectionChanged(final IWorkbenchPart part, final ISelection selection) {
		if (part == this) {
			return;
		}
		if (! (selection instanceof IStructuredSelection)) {
			return;
		}
		final IStructuredSelection sel = (IStructuredSelection)selection;
		final IFormatterItem[] items = FormatterManager.getManager().existingFavoritesFor(sel.iterator());
		if (items.length > 0) {
			viewer.setSelection(new StructuredSelection(items), true);
		}
	}

	private void createInlineEditor() {
		final TableViewerColumn column = new TableViewerColumn(viewer, nameColumn);
		column.setLabelProvider(new ColumnLabelProvider() {
			@Override
			public String getText(final Object element) {
				return ((IFormatterItem)element).getName();
			}
		});
		column.setEditingSupport(new EditingSupport(viewer) {
			TextCellEditor editor = null;

			@Override
			protected boolean canEdit(final Object element) {
				return true;
			}

			@Override
			protected CellEditor getCellEditor(final Object element) {
				if (editor == null) {
					final Composite table = (Composite)viewer.getControl();
					editor = new TextCellEditor(table);
				}
				return editor;
			}

			@Override
			protected Object getValue(final Object element) {
				return ((IFormatterItem)element).getName();
			}

			@Override
			protected void setValue(final Object element, final Object value) {
				((IFormatterItem)element).setName((String)value);
				viewer.refresh(element);
			}
		});
		viewer.getColumnViewerEditor().addEditorActivationListener(new AltClickCellEditListener());
	}

	/**
	 * Passing the focus request to the viewer's control.
	 */
	@Override
	public void setFocus() {
		viewer.getControl().setFocus();
	}

	/**
	 * For testing purposes only.
	 * 
	 * @return the table viewer in the Favorites view
	 */
	public TableViewer getFavoritesViewer() {
		return viewer;
	}

	/**
	 * Disposes of this view. Clients should not call this method; it is called by the Eclipse infrastructure.
	 * <p>
	 * This is the last method called on the view. At this point the part controls (if they were ever created) have been disposed
	 * as part of an SWT composite. There is no guarantee that createPartControl() has been called, so the part controls may never
	 * have been created.
	 * <p>
	 * Within this method a part may release any resources, fonts, images, etc.&nbsp; held by this part. It is also very important
	 * to deregister all listeners from the workbench.
	 */
	@Override
	public void dispose() {
		if (pageSelectionListener != null) {
			getSite().getPage().removePostSelectionListener(pageSelectionListener);
		}
		super.dispose();
	}

	/**
	 * Saves the view state such as sort order and filter state within a memento.
	 */
	@Override
	public void saveState(final IMemento memento) {
		super.saveState(memento);
		sorter.saveState(memento);
		filterAction.saveState(memento);
	}

	/**
	 * Initializes this view with the given view site. A memento is passed to the view which contains a snapshot of the views
	 * state such as sort order and filter state from a previous session. In our case, the sort and filter state cannot be
	 * initialized immediately, so we cache the memento for later.
	 */
	@Override
	public void init(final IViewSite site, final IMemento memento) throws PartInitException {
		super.init(site, memento);
		this.memento = memento;
	}

	/**
	 * Answer the currently selected files
	 */
	public IStructuredSelection getSelection() {
		return (IStructuredSelection)viewer.getSelection();
	}

	/**
	 * Report selection changes to the specified listener
	 * 
	 * @param selectionListener the listener (not <code>null</code>)
	 */
	public void addSelectionChangedListener(final ISelectionChangedListener listener) {
		viewer.addSelectionChangedListener(listener);
	}
}
